/**
 * @MixedXipDecoder.java  COPYRIGHT xiaojian
 * 
 * Created on 2017年6月26日 下午4:01:53
 */
package xj.toolkit.netty.handler.codec.tcp.decode;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.AttributeKey;
import xj.toolkit.bean.CommonsHeader;
import xj.toolkit.bean.bytebean.core.BeanFieldCodec;
import xj.toolkit.bean.bytebean.core.DecResult;
import xj.toolkit.bean.util.meta.Int2TypeMetainfo;
import xj.toolkit.util.ByteUtils;
import xj.toolkit.util.DefaultNumberCodecs;
import xj.toolkit.util.IdentifierFactory;
import xj.toolkit.util.MutableIdentifyable;

/**
 * 报文头采用xip格式进行编解码。
 * 
 * @author <a href="mailto:cxj2000@gmail.com">xiaojian.cao</a>
 *
 */
public class MixedXipDecoder extends ByteToMessageDecoder {

	private static final Logger logger = LoggerFactory.getLogger(MixedXipDecoder.class);

	private static final AttributeKey<CommonsHeader> HEADER_ATTRIBUTE_KEY = AttributeKey
			.valueOf("XIP_HEADER_ATTRIBUTE_KEY");

	// 大于1M的数据包可能是问题数据包
	private int maxMessageLength = 1024 * 1024;

	private int dumpBytes = 256;

	private boolean isDebugEnabled;

	private Int2TypeMetainfo xipTypeMetainfo;

	private BeanFieldCodec xipBeanCodec = null;

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
		CommonsHeader header = ctx.channel().attr(HEADER_ATTRIBUTE_KEY).get();
		if (null == header) {
			if (buffer.readableBytes() < CommonsHeader.HEADER_LENGTH) {
				return;
			}

			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("parse header... try parse...");
			}

			header = new CommonsHeader();
			header.setBasicVer(buffer.readByte());
			header.setPayloadType(buffer.readShort());
			header.setType(buffer.readShort());
			
			short idBytesLength = buffer.readShort();
			byte[] idBytes = new byte[idBytesLength];
			buffer.readBytes(idBytes);
			header.setIdBytes(ByteUtils.union(DefaultNumberCodecs.getBigEndianNumberCodec().short2Bytes(idBytesLength, 2), idBytes));
			
			header.setReserved(buffer.readInt());

			header.setMessageCode(buffer.readInt());

			header.setMessageLength(buffer.readInt());

			if (logger.isDebugEnabled() && isDebugEnabled) {
				logger.debug("header-->" + header);
			}

			if (header.getMessageLength() < 0) {
				logger.error("header.messageLength ({}) < 0, so drop this connection {}.", header.getMessageLength(),
						ctx.channel());
				ctx.channel().close().await();
				throw new RuntimeException("messageLength too large:" + header.getMessageLength());
			}
			if (maxMessageLength > 0) {
				if (header.getMessageLength() > maxMessageLength) {
					logger.error("header.messageLength ({}) exceed maxMessageLength[{}], so drop this connection{}.",
							header.getMessageLength(), maxMessageLength, ctx.channel());
					ctx.channel().close().await();
					throw new RuntimeException("messageLength too large:" + header.getMessageLength());
				}
			}

			ctx.channel().attr(HEADER_ATTRIBUTE_KEY).set(header);
		}

		if (buffer.readableBytes() < header.getMessageLength()) {
			logger.debug("readableBytes {} smaller than packageLength {},waiting for remain bytes",
					buffer.readableBytes(), header.getMessageLength());
			return;
		}

		// 为下一次在同一ctx上进行xip接受初始化环境
		ctx.channel().attr(HEADER_ATTRIBUTE_KEY).remove();

		final byte[] bytes = new byte[header.getMessageLength()];
		buffer.readBytes(bytes);

		if (logger.isTraceEnabled() && isDebugEnabled) {
			logger.trace("body raw bytes \r\n{}", ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		int msgCode = header.getMessageCode();
		Class<?> clazz = xipTypeMetainfo.find(msgCode);
		if (clazz == null) {
			throw new RuntimeException("invalid msgcode {" + msgCode + "}");
		}

		if (logger.isDebugEnabled() && isDebugEnabled) {
			logger.debug("xipBytes-->" + ByteUtils.bytesAsHexString(bytes, dumpBytes));
		}

		DecResult result = xipBeanCodec
				.decode(xipBeanCodec.getDecContextFactory().createDecContext(bytes, clazz, null, null));
		if (result != null) {
			Object xipBean = result.getValue();
			if (xipBean instanceof MutableIdentifyable) {
				((MutableIdentifyable) xipBean)
				.setIdentification(IdentifierFactory.fromByte(header.getIdBytes()));
			}
			out.add(xipBean);
			return;
		}

		logger.error("Decode result is null, class[" + clazz + "].");
	}

	public int getMaxMessageLength() {
		return maxMessageLength;
	}

	public void setMaxMessageLength(int maxMessageLength) {
		this.maxMessageLength = maxMessageLength;
	}

	public int getDumpBytes() {
		return dumpBytes;
	}

	public void setDumpBytes(int dumpBytes) {
		this.dumpBytes = dumpBytes;
	}

	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	public Int2TypeMetainfo getXipTypeMetainfo() {
		return xipTypeMetainfo;
	}

	public void setXipTypeMetainfo(Int2TypeMetainfo xipTypeMetainfo) {
		this.xipTypeMetainfo = xipTypeMetainfo;
	}

	public BeanFieldCodec getXipBeanCodec() {
		return xipBeanCodec;
	}

	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

}
